﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Adalbertus.Extensions;

namespace Adalbertus.BudgetPlanner.Domain
{
    public class Budget
    {
        public virtual int Id { get; private set; }
        public virtual DateTime DateFrom { get; set; }
        public virtual DateTime DateTo { get; set; }
        public virtual bool IsDraft { get; set; }
        public virtual IList<IncomeEntry> Incomes { get; private set; }
        public virtual IList<SavingEntry> Savings { get; private set; }
        public virtual IList<BudgetPlan> BudgetPlanItems { get; private set; }
        public virtual IList<Expense> Expenses { get; private set; }


        public Budget()
        {
            Incomes = new List<IncomeEntry>();
            Savings = new List<SavingEntry>();
            BudgetPlanItems = new List<BudgetPlan>();
            Expenses = new List<Expense>();
        }

        public static Budget CreateEmptyForNextMonth(DateTime contextDate, IEnumerable<CashFlow> cashFlows)
        {
            Contract.Requires<ArgumentNullException>(cashFlows != null);
            Contract.Ensures(Contract.Result<Budget>() != null);
            DateTime dateFrom = new DateTime(contextDate.AddMonths(1).Year, contextDate.AddMonths(1).Month, 1);
            DateTime dateTo = dateFrom.AddMonths(1).AddDays(-1);
            Budget budget = new Budget
            {
                DateFrom = dateFrom,
                DateTo = dateTo,
                IsDraft = true,
            };

            foreach (CashFlow cashFlowType in cashFlows)
            {
                budget.BudgetPlanItems.Add(new BudgetPlan { Flow = cashFlowType, Budget = budget });
            }

            return budget;
        }

        public virtual IncomeEntry AddIncomeEntry(Income income, decimal value, DateTime date)
        {
            Contract.Requires<ArgumentNullException>(income != null, "income");
            Contract.Ensures(Contract.Result<IncomeEntry>().Income.Equals(income));

            IncomeEntry incomeEntry = income.AddIncomeEntry(value, date);
            Incomes.Add(incomeEntry);
            return incomeEntry;
        }

        public virtual void RemoveIncomeEntry(IncomeEntry incomeEntry)
        {
            Contract.Requires<ArgumentNullException>(incomeEntry != null, "incomeEntry");
            Incomes.Remove(incomeEntry);
        }

        public virtual SavingEntry WithdrawSavingEntry(Saving saving, decimal value, DateTime date)
        {
            SavingEntry savingEntry = saving.Withdraw(value, date);
            Savings.Add(savingEntry);
            return savingEntry;
        }

        public virtual void CancelWithdrawSavingEntry(SavingEntry savingEntry)
        {
            Contract.Requires<ArgumentNullException>(savingEntry != null, "savingEntry");
            Savings.Remove(savingEntry);
        }

        public virtual Expense AddExpense(CashFlow flow, decimal value, string description, DateTime date)
        {
            Contract.Requires<ArgumentNullException>(flow != null, "flow");
            Contract.Requires<ArgumentException>(value > 0, "value");
            Contract.Requires<ArgumentException>(date.IsBetween(DateFrom, DateTo));
            Contract.Ensures(Contract.Result<Expense>() != null);
            Contract.Ensures(Contract.Result<Expense>().Flow == flow);
            Contract.Ensures(Contract.Result<Expense>().Value == value);
            Contract.Ensures(Contract.Result<Expense>().Description == description);
            Contract.Ensures(Contract.Result<Expense>().Date.IsBetween(DateFrom, DateTo));

            Expense expense = new Expense
            {
                Description = description,
                Flow = flow,
                Value = value,
                Date = date
            };
            Expenses.Add(expense);
            return expense;
        }
    }
}
